home *** CD-ROM | disk | FTP | other *** search
/ Delphi Developer's Kit 1996 / Delphi Developer's Kit 1996.iso / power / bmh11a / bmh111a.pas next >
Encoding:
Pascal/Delphi Source File  |  1995-12-22  |  15.4 KB  |  421 lines

  1. Date sent:            Wed, 5 Jul 1995 09:28:13 -0230
  2. From:             "Jody R. Cairns" <jodyc@cs.mun.ca>
  3. Subject:          New version of search unit (bmh.pas)
  4. To:               cwhite@teleport.com
  5.  
  6. Please replace your current version of bmh.pas with the following.
  7. And if you are going to ZIP it up, please name it bmh111a.zip, to
  8. indicate the version number of the unit.
  9. The latest version is 1.11a.  This should be the last update of awhile.
  10.  
  11. Thanks,
  12.  
  13. Jody
  14.  
  15. ****************************
  16. * Jody R. Cairns           *
  17. * jodyc@cs.mun.ca          *
  18. ****************************
  19.  
  20. unit Bmh;
  21. {
  22. BMH 1.11a, Copyright (c) 1995, by Jody R. Cairns (jodyc@cs.mun.ca)
  23.  
  24. This unit implements the Boyer-Moore-Horspool pattern searching
  25. algorithm as taken from the 'Handbook of Algorithms and Data Structures
  26. in Pascal and C', Second Edition, by G.H Gonnet and R. Baeza-Yates.
  27.  
  28. The algorithm searches for a pattern within a buffer.  I have added
  29. functions to implement the searches with files of any type.
  30.  
  31. Implementation:  I designed this unit for two types of users - those
  32.   who want complete control of their code (Non-lazy), and those who
  33.   don't (Lazy).  "Non-lazy" users have to manually set some
  34.   variables that are essential for the algorithm.  "Lazy" users
  35.   don't have to do anything except call a function.
  36.  
  37. 1) Lazy:  the following function returns the offset in which
  38. the specified string is found with the specified file.  A value
  39. of -1 is returned if the string is not found:
  40. function GetStringOffset (StrToFind: string; const FileName: TFileName; const IgnoreCase: boolean): longint;
  41. - StrToFind: the string you are looking for.
  42. - FileName: the name of the file to search for StrToFind.
  43. - IgnoreCase: indicates whether a case-sensitive search is done or not.
  44.               The global variable IgnoreCase is true by default.
  45.   Examples:
  46.   a)  IgnoreCase := true;
  47.       if GetStringOffset('path','C:\AUTOEXEC.BAT',IgnoreCase) <> -1 then
  48.         Text1.Caption := 'Found expression'
  49.       else
  50.         Test1.Caption := 'Expression not found';
  51.   b)  if GetStringOffset('PROMPT','C:\AUTOEXEC.BAT',false) <> -1 then
  52.         Text1.Caption := 'Found expression'
  53.       else
  54.         Test1.Caption := 'Expression not found';
  55.  
  56.  
  57. 2) Non-lazy:  you should see the function GetStringOffset for everything
  58. that needs to be done before FindString is called, which is the main
  59. function that opens the file for searching.  GetStringOffset creates
  60. a buffer each time it is called.  However, you need only they do this
  61. once.  Also, the procedure MakeBMHTable must be called for each DIFFERENT
  62. string to be searched for.  You needn't call MakeBMTable everytime the
  63. same string is searched for.
  64. REQUIREMENTS:
  65.   a)  Call CreateBuffer which allocates memory for the buffer to
  66.       be used whe reading files.  The buffer is a global variable called
  67.       Buffer of type TSearchBuffer.  CreateBuffer returns the amount of
  68.       memory allocated for Buffer.
  69.   b)  If you DO NOT want to do a case-insensitive search, set the
  70.       global variable IgnoreCase to FALSE.  By default, IgnoreCase is
  71.       TRUE, which means all searches are case-insensitive.  If you choose
  72.       a case-insensitive search, make sure your string is converted
  73.       to uppercase!  That is, do this:  MyString := uppercase(MyString);
  74.   c)  Call MakeBMHTable(MyString) to create the index table for the string
  75.       to be searched for (MyString).  This MUST be called for every
  76.       DIFFERENT string to be searched for.  However, it need only be
  77.       called ONCE for each different string.  The index table is a
  78.       global variable called BMHTable of type TBMHTable.  If you're doing
  79.       a case-insensitive search, MyString MUST be uppercased BEFORE
  80.       MakeBMHTable is called.
  81.   d)  Call FindString(MyString, MyFile) to search for MyString within the
  82.       file MyFile.  FindString returns the offset position of MyString
  83.       within MyFile if it is found; otherwise it returns -1.  A REMINDER:
  84.       if you're doing a case-insensitive search, make sure MyString is
  85.       converted to uppercase!
  86.   e)  Call DestroyBuffer to free the memory that Buffer points to.
  87.  
  88. Note that I only do (a), (b) and (c) ONCE.  Once I search for a different
  89. string, then I MUST do (c) again.  And (e) need only be called once
  90. after ALL searching is completed.
  91. Summary: a) Allocate memory for Buffer by calling CreateBuffer.
  92.          b) Convert MyString (string I want to find) to uppercase if
  93.             I'm doing a case-insensitive search, which is the default.
  94.             Otherwise, set IgnoreCase := false and leave MyString alone.
  95.          c) Call MakeBMHTable with MyString.
  96.          d) Call FindString with MyString and name of file.
  97.          e) Remember to release memory Buffer is pointing to by calling
  98.             DestroyBuffer.
  99.  
  100.  
  101. VERSION CHANGES:
  102.   1.11a - removed second boolean condition from REPEAT-UNTIL loop in
  103.           function DOBMHSearch, which increases execution speed.  I
  104.           should have done this in version 1.10.
  105.  
  106.   1.11  - added an important comment about case-insensitive searches that
  107.           was not mentioned in previous versions: for "non-lazy" users,
  108.           if you are doing a case-insensitive search, make sure the
  109.           string you are searching for (i.e. that is passed in function
  110.           FindString) is converted to uppercase; otherwise, the search
  111.           may fail.
  112.  
  113.   1.10  - improved function DOBMHSearch execution speed by replacing
  114.           inner WHILE statement with a FOR-loop and a GOTO statement.
  115.         - improved function FindString execution speed by adding
  116.           BREAK statement if pattern was found.
  117.         - added case-insensitive search option.  The global variable
  118.           IgnoreCase was added to indicate the search type to be
  119.           performed, and procedure UpCaseBuffer was added.
  120.  
  121.   1.01  - added additional explanatory comments
  122.         - added a couple more error strings to the function GetError
  123.  
  124.   1.00  - original release
  125.  
  126.  
  127. NOTES:
  128.  
  129. - if you have ANY questions, problems or suggestions, please feel free
  130.   to contact me at jodyc@cs.mun.ca
  131.  
  132. - various code optimizations can be made to improve speed.
  133.  
  134. - minimal error-checking is performed.  I would add more to suit your
  135.   own particular needs.
  136.  
  137. - all the routines in this unit could be gathered into an object of
  138.   some sort.  I may do that later.
  139.  
  140. - to search Read-Only files, you should set system.filemode := 0 before
  141.   FindString is called; otherwise, FindString will fail.
  142.  
  143. - currently, the algorithm only finds the first occurrence of a pattern
  144.   within a file.  I plan to extend this to search for ALL occurrences.
  145.  
  146.  
  147. This unit is FreeWare.  You may use it freely at your own risk.  Being
  148. FreeWare, this unit is not to be sold at any charge, whether it is used
  149. alone or incorporated into any program.
  150.  
  151. Please feel free to add any enhancements or modifications.  If you do,
  152. just add your credits along with mine.  And I'd be interested in any
  153. modifications you do make.  Any enhancement/modification must also be
  154. released as Freeware.
  155.  
  156. Jody R. Cairns
  157. jodyc@cs.mun.ca
  158.  
  159. }
  160.  
  161. {$Q-,I+,R-,S-,B-,V-,D-,L-}
  162.  
  163. interface
  164.  
  165. uses
  166.   SysUtils;
  167.  
  168. const
  169.   MaxBufferSize = 1024 * 63;  { Maximum size of buffer }
  170.  
  171. type
  172.   TBMHTable = array[0..255] of byte;
  173.   TSearchBuffer = ^TSearchBufferArray;
  174.   TSearchBufferArray = array[1..MaxBufferSize] of char;
  175.  
  176.   function  CreateBuffer: word;
  177.   procedure DestroyBuffer;
  178.   procedure MakeBMHTable (const StrToFind: string);
  179.   function  FindString (const StrToFind: string; const FileName: TFileName): longint;
  180.   function  GetStringOffset (StrToFind: string; const FileName: TFileName; const IgnoreCase: boolean): longint;
  181.  
  182. const
  183.   IgnoreCase: boolean = true; { determines whether to do case-insensitive
  184.                                 search or not }
  185. var
  186.   BMHTable: TBMHTable;      { index table required for B-M-H algorithm }
  187.   Buffer  : TSearchBuffer;  { buffer used when reading file }
  188.  
  189.  
  190. implementation
  191. { NOTES:
  192.   - I use no local variables within procedures and functions.  Local
  193.     variables tend to slow execution too much for my taste, since
  194.     most local variables have to be created on the system stack each
  195.     time a function is called.
  196. }
  197.  
  198. uses
  199.    WinProcs, WinTypes, Dialogs;
  200.  
  201. var
  202.   FileToSearch: file;  { file to search for given string }
  203.   OldFileMode: byte;   { saves the file mode access code }
  204.   K: integer;
  205.   I,J,
  206.   BytesRead,           { number of bytes read into buffer for blockread }
  207.   OldErrorCode: word;  { saves Windows critical error-handling mode }
  208.  
  209.  
  210. procedure UpCaseBuffer (var Buffer: TSearchBufferArray; const Size: word); assembler;
  211. { Converts all lower-case characters within Buffer to upper-case }
  212. asm
  213.   mov  cx, Size         { Load size of Buffer }
  214.   jcxz @3               { Exit if size = 0 }
  215.   les  di, Buffer       { Load Buffer }
  216. @1:
  217.   mov  al, es:[di]      { Check current byte of Buffer }
  218.   cmp  al, 'a'          { Skip if not 'a'..'z' }
  219.   jb   @2
  220.   cmp  al, 'z'
  221.   ja   @2
  222.   sub  al, 20h          { Convert to uppercase }
  223.   mov  es:[di], al      { Put converted byte back in Buffer }
  224. @2:
  225.   inc  di               { Get next byte in Buffer }
  226.   loop @1               { Continue to size of Buffer }
  227. @3:
  228. end;
  229.  
  230.  
  231. function GetError (const ErrorCode: integer): string;
  232. { Returns a string pertaining to the type of error.  ErrorCode can be
  233.   taken from IOResult if IO-checking is off, or from an exception handler.
  234.   The strings listed below are taken from Borland's 'Object Pascal
  235.   Language Guide' for Delphi Version 1.0, pages 273-275.
  236. }
  237. begin
  238.   case ErrorCode of
  239.      2: Result := 'File not found';
  240.      3: Result := 'Path not found';
  241.      4: Result := 'Too many open files';
  242.      5: Result := 'File access denied';
  243.      6: Result := 'Invalid file handle';
  244.     12: Result := 'Invalid file access code';
  245.     15: Result := 'Invalid drive';
  246.    100: Result := 'Disk read error';
  247.    101: Result := 'Disk write error';
  248.    102: Result := 'File not assigned';
  249.    103: Result := 'File not open';
  250.   else
  251.     Result := ''
  252.   end
  253. end;
  254.  
  255.  
  256. function DoBMHSearch(const StrToFind: string): longint;
  257. { Performs the Boyer-Moore-Horspool string searching algorithm, returning
  258.   the offset in buffer where the string was found.  If not found, then
  259.   -1 is returned.  Adapted from the 'Handbook of Algorithms and Data
  260.   Structures in Pascal and C', Second Edition, by G.H Gonnet and
  261.   R. Baeza-Yates.
  262. }
  263. label
  264.   NotFound;  { using a goto statement improves speed }
  265. begin
  266.   Result := -1;
  267.   J := length(StrToFind);
  268.   while (J <= BytesRead) do
  269.     begin
  270.       I := J;
  271.       for K := length(StrToFind) downto 1 do
  272.         begin
  273.           if Buffer^[I] <> StrToFind[K] then
  274.             goto NotFound;
  275.           dec (I)
  276.         end;
  277.       Result := (BytesRead - I);
  278.       exit;
  279.     NotFound:
  280.       inc(J,BMHTable[ord(Buffer^[J])])
  281.     end { while }
  282. end;
  283.  
  284.  
  285. procedure MakeBMHTable (const StrToFind: string);
  286. { Creates a Boyer-Moore-Horspool index table for the search string
  287.   StrToFind in the table BMHTable.  This MUST be called before
  288.   the string is searched for.  But it only needs to be called once
  289.   for each different string.
  290. }
  291.   begin
  292.     fillchar (BMHTable,sizeof(BMHTable),length(StrToFind));
  293.     for K := 1 to (length(StrToFind) - 1) do
  294.       BMHTable[ord(StrToFind[K])] := (length(StrToFind) - K)
  295.   end;
  296.  
  297.  
  298. function CreateBuffer: word;
  299. { Creates a buffer for the FindString function.  The buffer is
  300.   divisable by 1024 because most disk clusters are divisible by
  301.   1024, which makes for faster reads.  The size of the buffer created
  302.   is returned.  Zero (0) is returned if the buffer was not created.
  303. }
  304. begin
  305.   if MaxAvail >= MaxBufferSize then
  306.     Result := MaxBufferSize
  307.   else
  308.     Result := (MaxAvail div 1024) * 1024;
  309.   try { allocate memory }
  310.     getmem (Buffer, Result)
  311.   except
  312.     Result := 0
  313.   end { allocate memory }
  314. end;
  315.  
  316. procedure DestroyBuffer;
  317. { Free the memory that Buffer points to }
  318. begin
  319.   freemem(Buffer,sizeof(Buffer^))
  320. end;
  321.  
  322.  
  323. function FindString (const StrToFind: string; const FileName: TFileName): longint;
  324. { Opens file to initiate Boyer-Moore-Horspool search algorithm, reading
  325.   blocks of data from file until string is found or all bytes are read.
  326.   The offset within FileName is returned if StrToFind is found; otherwise,
  327.   -1 is returned.  Note that the offset is zero-based.  The first byte
  328.   in a file is at offset 0.  The second byte is at offset 1.  Etc.
  329.   **** BEFORE FUNCTION IS CALLED ****:
  330.   1) a variable called Buffer of type TSearchBuffer MUST exist with a size
  331.      greater than zero (0). NO error-checking is done on this.
  332.   2) a variable called BMHTable of type TBMHTable must exist and be
  333.      initialized for the string StrToFind using procedure MakeBMHTable.
  334.   3) if IgnoreCase is true (i.e. you are doing a case-insensitive search),
  335.      make sure StrToFind is converted to uppercase.
  336. }
  337. begin
  338.   Result := -1;
  339.   assignfile (FileToSearch,FileName);
  340.   try { to open file to search }
  341.     reset(FileToSearch,1);
  342.     try { searching for string }
  343.       repeat
  344.         blockread(FileToSearch,Buffer^,sizeof(Buffer^),BytesRead);
  345.         { Convert all appropiate chars to uppercase if search is
  346.           case-insensitive.
  347.         }
  348.         if IgnoreCase then
  349.           UpCaseBuffer(Buffer^,BytesRead);
  350.  
  351.         { Search for string within buffer }
  352.         Result := DoBMHSearch(StrToFind);
  353.         { Calculate offset position in file if found }
  354.         if Result <> -1 then
  355.           begin
  356.             Result := filepos(FileToSearch) - Result;
  357.             { Adding the following statement improves speed because
  358.               the UNTIL condition is not evaluated.
  359.             }
  360.             break
  361.           end;
  362.         { If Buffer is full, skip back length(StrToFind) bytes in file.
  363.           This ensures any pattern isn't "cut-off" during readblock.
  364.         }
  365.         if BytesRead = sizeof(Buffer^) then
  366.           seek(FileToSearch,filepos(FileToSearch)-length(StrToFind))
  367.       until (BytesRead = 0);
  368.     finally
  369.       closefile(FileToSearch)
  370.     end; { searching for string }
  371.   except
  372.     on E: EInOutError do
  373.       begin
  374.         MessageDlg('Cannot scan ' + uppercase(FileName) + '.'#13 + GetError(E.ErrorCode)+'.',mterror,[mbOK],0);
  375.         Result := -1
  376.       end
  377.   end { opening file to search }
  378. end;
  379.  
  380.  
  381. function GetStringOffset (StrToFind: string; const FileName: TFileName; const IgnoreCase: boolean): longint;
  382. { This is for you "lazy" programmers.  This function does all initialization
  383.   routines to find StrToFind within FileName.  If StrToFind is found, the
  384.   offset location within FileName is returned; otherwise, -1 is returned,
  385.   indicating an unsuccessful search.
  386. }
  387. begin
  388.   { try to create buffer for blockread procedure }
  389.   if CreateBuffer = 0 then
  390.     begin
  391.       MessageDlg('Not enough memory to perform search.',mtWarning,[mbOK],0);
  392.       Result := -1;
  393.       exit
  394.     end;
  395.  
  396.   { Convert to uppercase for case-insensitive searching }
  397.   if IgnoreCase then
  398.     StrToFind := uppercase(StrToFind);
  399.  
  400.   { This must be done for every string }
  401.   MakeBMHTable(StrToFind);
  402.  
  403.   { Enable reading of read-only files }
  404.   OldFileMode := system.filemode;
  405.   system.filemode := 0;
  406.  
  407.   { Turn off critical windows handling }
  408.   olderrorcode := SetErrorMode(SEM_FAILCRITICALERRORS);
  409.  
  410.   try { to search file for string }
  411.     Result := FindString (StrToFind, FileName)
  412.   finally { clean-up }
  413.     DestroyBuffer;
  414.     system.filemode := OldFileMode;
  415.     SetErrorMode(OldErrorCode)
  416.   end { searching }
  417. end;
  418.  
  419. end. { bmh }
  420.  
  421.